package mnn;

/*
 This program is written by Ahmed Medhat Ahmed
 Egypt
 Alexandria Universty
 Faculty of Science
 Computer Science
 7-7-2007
 a.medhat.cs@gmail.com
 ama_compsc@yahoo.com
 */

import java.util.Random;

public class ANN {
	static public double sigmoid(double x) {
		double sig = 0.0;
		sig = (1.0 / (1.0 + Math.exp(-x)));
		return sig;
	}

	private int INPUT; // default input neurons
	private int OUTPUT; // default output neurons
	private int HIDDENLAYER; // default hidden layers
	private int HIDDENNEURON; // default hidden neurons per layer
	private double LEARNINGRATE;

	private double MOMENTUMRATE;
	public double w[][][];
	/*
	 * Array of weight values on connection lines first dimention : is for order
	 * of connection layer second dimention : is for the neuron number of lower
	 * layer or the connection line it comes from third dimention : is for
	 * neuron no of upper layer or the connection line it is going to
	 */
	private double momentum[][][];
	/*
	 * Array of momentum values the momentum value on each iteration
	 * (n)=momentum rate * modified weight(n-1), the momentume dependes on
	 * weights so it has three dimentions 1st for the connection layer order ,
	 * 2nd for the lower layer , 3rd for the upper layer of the connection line
	 */
	private double n[][];
	/*
         
         */
	private int neuronlayers;
	private int connectionlayers;
	private int neuroncount[];
	private int maxneurons;
	private Random random;

	public ANN(int input, int output, int hiddenlayer, int hiddenneuron,
			double learnrate, double momentumrate, Random rand) {
		INPUT = input;
		OUTPUT = output;
		HIDDENLAYER = hiddenlayer;
		HIDDENNEURON = hiddenneuron;
		LEARNINGRATE = learnrate;
		MOMENTUMRATE = momentumrate;

		if (rand != null) {
			random = rand;
		} else {
			random = new Random();
		}

		neuronlayers = HIDDENLAYER + 2;// hiddenlayer + input +ouput
		connectionlayers = neuronlayers - 1;
		neuroncount = new int[neuronlayers];

		neuroncount[0] = INPUT + 1;// input neurons + static threshold

		for (int l = 1; l < neuronlayers - 1; l++) {
			neuroncount[l] = HIDDENNEURON;
		}
		neuroncount[neuronlayers - 1] = OUTPUT;

		maxneurons = Math.max((Math.max(INPUT + 1, OUTPUT)), HIDDENNEURON);
		w = new double[connectionlayers][maxneurons][maxneurons];
		momentum = new double[connectionlayers][maxneurons][maxneurons];
		n = new double[neuronlayers][maxneurons];
		int i, j, k;
		for (i = 0; i < connectionlayers; i++) {
			for (j = 0; j < neuroncount[i]; j++) {
				for (k = 0; k < neuroncount[i + 1]; k++) {
					w[i][j][k] = (random.nextDouble() * 2.0) - 1.0; // random
																	// weights
					momentum[i][j][k] = 0.0; // intial momentum =0
				}
			}
		}

		for (i = 0; i < neuronlayers; i++) {
			for (j = 0; j < neuroncount[i]; j++) {
				n[i][j] = 0.0;
			}
		}
		n[0][INPUT] = 1.0; // static threshold
	}

	void backprop(double[] input, double[] train_o) {
		feedforward(input);

		double[] desired_o = new double[OUTPUT];

		for (int o = 0; o < OUTPUT; o++) {
			// desired_o[o] = sigmoid(train_o[o]);
			desired_o[o] = train_o[o];
		}

		double delta[][] = new double[neuronlayers][maxneurons];

		// output layer delta calculation
		for (int o = 0; o < OUTPUT; o++) {
			delta[neuronlayers - 1][o] = (desired_o[o] - n[neuronlayers - 1][o]);
		}

		// hidden layer delta calculation
		for (int i = neuronlayers - 2; i > 0; i--) {
			for (int j = 0; j < neuroncount[i]; j++) {
				double deltasum = 0;
				for (int k = 0; k < neuroncount[i + 1]; k++) {
					deltasum += delta[i + 1][k] * w[i][j][k] * n[i + 1][k]
							* (1.0 - n[i + 1][k]);
				}
				delta[i][j] = deltasum;
			}
		}

		double weightchange = 0.0;

		// adjust weights starting from input
		for (int i = 0; i < connectionlayers; i++) {
			for (int j = 0; j < neuroncount[i]; j++) { // no of in nodes
				for (int k = 0; k < neuroncount[i + 1]; k++) {// out nodes
					weightchange = (delta[i + 1][k] * n[i][j] * n[i + 1][k] * (1.0 - n[i + 1][k]));
					w[i][j][k] += (LEARNINGRATE * (((1.0 - MOMENTUMRATE) * weightchange) + (MOMENTUMRATE * momentum[i][j][k])));
					momentum[i][j][k] = weightchange;
				}
			}
		}

	}

	double[] feedforward(double[] input) {
		// input layers
		for (int l = 0; l < INPUT; l++) {
			n[0][l] = sigmoid(input[l]);
			// n[0][l]=input[l];
		}
		n[0][INPUT] = sigmoid(1.0); // static threshold
		// n[0][INPUT]=1.0; //static threshold

		// hidden+output layers
		int i, j, k;
		for (i = 1; i < neuronlayers; i++) {
			for (k = 0; k < neuroncount[i]; k++) {
				double sigsum = 0;
				for (j = 0; j < neuroncount[i - 1]; j++) {
					sigsum += (n[i - 1][j] * w[i - 1][j][k]);
				}
				n[i][k] = sigmoid(sigsum);
			}
		}

		double[] result = new double[OUTPUT];
		for (int r = 0; r < OUTPUT; r++) {
			result[r] = n[neuronlayers - 1][r];
		}
		return result;
	}

	public void func1(int[] con, int[] nlayer, int[] maxneur) {
		con[0] = connectionlayers;
		nlayer[0] = neuronlayers;
		maxneur[0] = maxneurons;

	}

	public void func2(int[] ncount) {
		int i;
		for (i = 0; i < neuronlayers; i++) {
			ncount[i] = neuroncount[i];
		}

	}

	public double[] getResult(double input[]) {
		return feedforward(input);
	}

	public void getwaites(double[][][] x) {
		int i, j, k;
		for (i = 0; i < connectionlayers; i++) {
			for (j = 0; j < neuroncount[i]; j++) {
				for (k = 0; k < neuroncount[i + 1]; k++) {
					x[i][j][k] = w[i][j][k];

				}
			}
		}

	}

	public void setwaites(double[][][] x) {
		int i, j, k;
		for (i = 0; i < connectionlayers; i++) {
			for (j = 0; j < neuroncount[i]; j++) {
				for (k = 0; k < neuroncount[i + 1]; k++) {
					w[i][j][k] = x[i][j][k];

				}
			}
		}
	}

	@Override
	public String toString() {
		String string = "ANN:\n";
		int i, j, k;
		for (i = 0; i < connectionlayers; i++) {
			for (j = 0; j < neuroncount[i]; j++) {
				for (k = 0; k < neuroncount[i + 1]; k++) {
					string += "w " + i + "," + j + "," + k + " = " + w[i][j][k]
							+ "\n";
				}
			}
		}
		for (i = 0; i < neuronlayers; i++) {
			for (j = 0; j < neuroncount[i]; j++) {
				string += "n " + i + " , " + j + " = " + n[i][j] + " \n";
			}
		}

		return string;
	}

	public void Train(double[] input, double[] output) {
		backprop(input, output);
	}
}// class ANN
